NestJS API Versioning With Swagger

aliases
No value
tags
nestjs
description
No value
links
Nest.js ๐Ÿฑ https://chatgpt.com/s/dr_6826d5e83ca481918153c428cd2e2645
status
ai response
project
false
area
true
resource
false
title
NestJS API Versioning With Swagger
created
2025-05-16T15:03:41
updated
2025-05-16T15:06:41

Written by Chat GPT Deep Research

NestJS API ๋ฒ„์ €๋‹๊ณผ Swagger ์—ฐ๋™

NestJS๋Š” REST API๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋„๋ก API ๋ฒ„์ „ ๊ด€๋ฆฌ(Versioning) ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ @nestjs/swagger ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๋ฉด API ๋ฌธ์„œ๋ฅผ ์†์‰ฝ๊ฒŒ ์ž๋™ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” URI ๊ธฐ๋ฐ˜์˜ API ๋ฒ„์ €๋‹์„ NestJS์—์„œ ๊ตฌํ˜„ํ•˜๊ณ  Swagger(OpenAPI) ๋ฌธ์„œํ™”์™€ ํ†ตํ•ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‹ค๋ฌด ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ๋‹จ์ผ Swagger ๋ฌธ์„œ๋กœ ๋ชจ๋“  ๋ฒ„์ „์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ, ๋‹ค์ค‘ Swagger ๋ฌธ์„œ๋กœ ๋ฒ„์ „๋ณ„(๋˜๋Š” ๋ชจ๋“ˆ๋ณ„)๋กœ ๋ฌธ์„œ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ์ „๋žต์„ ๊ตฌํ˜„ํ•˜๊ณ  ๋น„๊ตํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

URI ๊ธฐ๋ฐ˜ API ๋ฒ„์ „ ๊ด€๋ฆฌ ์„ค์ • (main.ts)

NestJS์—์„œ๋Š” ๊ฐ„๋‹จํ•œ ์„ค์ •์œผ๋กœ URI์— ๋ฒ„์ „ ์ •๋ณด๋ฅผ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. URI ๋ฒ„์ „ ๊ด€๋ฆฌ๋Š” URL ๊ฒฝ๋กœ์— /v1, /v2์ฒ˜๋Ÿผ ๋ฒ„์ „ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฒ„์ „์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์šฐ์„  main.ts ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ํŒŒ์ผ์—์„œ ๋ฒ„์ „ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Nest ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด app.enableVersioning()์„ ํ˜ธ์ถœํ•˜๋ฉฐ, type: VersioningType.URI ์˜ต์…˜์„ ์ง€์ •ํ•˜๋ฉด URI ๊ธฐ๋ฐ˜ ๋ฒ„์ €๋‹์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

ํŠนํžˆ ์ค‘์š”ํ•œ ์ ์€ Swagger ๋ฌธ์„œ๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์ „์— ๋จผ์ € enableVersioning์„ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” NestJS Swagger ๋ชจ๋“ˆ์˜ ์ด์Šˆ์—์„œ๋„ ๊ฐ•์กฐ๋œ ๋ถ€๋ถ„์œผ๋กœ, ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ๋ฉด Swagger๊ฐ€ ๋ฒ„์ „์ด ๋ถ™์€ ๊ฒฝ๋กœ๋“ค์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ main.ts์˜ ์˜ˆ์‹œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { VersioningType } from '@nestjs/common';
import { setupSwagger } from './setupSwagger';  // Swagger ์„ค์ • ํ•จ์ˆ˜ import

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // (์ค‘์š”) Swagger ์„ค์ • ์ „์— API ๋ฒ„์ „ ๊ด€๋ฆฌ ํ™œ์„ฑํ™”
  app.enableVersioning({ type: VersioningType.URI });

  // Swagger ๋ฌธ์„œ ์„ค์ •
  setupSwagger(app);

  await app.listen(3000);
}
bootstrap();

์œ„ ์ฝ”๋“œ์—์„œ app.enableVersioning({ type: VersioningType.URI }) ํ•œ ์ค„๋กœ ์ „์—ญ์ ์ธ URI ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ์ด์ œ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ๋ผ์šฐํŠธ์— ๋ฒ„์ „ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋‹ฌ๋ฉด ์š”์ฒญ ๊ฒฝ๋กœ์— ํ•ด๋‹น ๋ฒ„์ „์ด ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค (์˜ˆ: /v1/..., /v2/...).

๋ฒ„์ „๋ณ„ ์ปจํŠธ๋กค๋Ÿฌ ๊ตฌํ˜„ (์˜ˆ: ReservationV2Controller)

์ด์ œ ํŠน์ • ์ปจํŠธ๋กค๋Ÿฌ์— ๋ฒ„์ „์„ ์ง€์ •ํ•˜์—ฌ ๋ฒ„์ „์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋™์ž‘์„ ํ•˜๋„๋ก ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. NestJS์—์„œ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ๊ฐœ๋ณ„ ๊ฒฝ๋กœ ํ•ธ๋“ค๋Ÿฌ์— @Version() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— { version: '๊ฐ’' } ์˜ต์…˜์„ ์ค˜์„œ ํ•ด๋‹น ๋ฒ„์ „์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด v2 ๋ฒ„์ „์˜ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ReservationV2Controller๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

// reservation.v2.controller.ts
import { Controller, Get, Post, Param } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';

@Controller({ path: 'reservations', version: '2' })
@ApiTags('Reservations')
export class ReservationV2Controller {
  @Get()
  @ApiOperation({ summary: '๋ชจ๋“  ์˜ˆ์•ฝ ๋ชฉ๋ก ์กฐํšŒ (v2)' })
  findAllV2() {
    // ์˜ˆ์•ฝ ๋ชฉ๋ก ์กฐํšŒ v2 ๋กœ์ง...
  }

  @Get(':id')
  @ApiOperation({ summary: 'ID๋กœ ์˜ˆ์•ฝ ์กฐํšŒ (v2)' })
  findByIdV2(@Param('id') id: string) {
    // ํŠน์ • ID ์˜ˆ์•ฝ ์กฐํšŒ v2 ๋กœ์ง...
  }

  @Post()
  @ApiOperation({ summary: '์ƒˆ ์˜ˆ์•ฝ ์ƒ์„ฑ (v2)' })
  createV2() {
    // ์˜ˆ์•ฝ ์ƒ์„ฑ v2 ๋กœ์ง...
  }
}

์œ„ ์ฝ”๋“œ์—์„œ๋Š” @Controller({ path: 'reservations', version: '2' })๋ฅผ ํ†ตํ•ด ์ด ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋ชจ๋“  ๊ฒฝ๋กœ๊ฐ€ v2๋กœ ๋ฒ„์ „์ด ์ง€์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์˜ˆ๋ฅผ ๋“ค์–ด findAllV2() ๋ฉ”์„œ๋“œ๋Š” GET ์š”์ฒญ์œผ๋กœ /v2/reservations ๊ฒฝ๋กœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. (ReservationV1Controller์—์„œ๋Š” version์„ '1'๋กœ ์ง€์ •ํ•˜๊ฑฐ๋‚˜ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ด /v1/reservations๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.) ๊ฐ ์ปจํŠธ๋กค๋Ÿฌ์— @ApiTags ๋“ฑ์„ ์ง€์ •ํ•˜๋ฉด Swagger ๋ฌธ์„œ์—์„œ ํƒœ๊ทธ๋กœ ๊ตฌ๋ถ„ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๊ฐ ๋ฒ„์ „๋ณ„๋กœ ๋ณ„๋„ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด ๋ฒ„์ „ ์ƒ์Šน์— ๋”ฐ๋ฅธ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ƒˆ๋กœ์šด ์ปจํŠธ๋กค๋Ÿฌ/๋ฉ”์„œ๋“œ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด ๊ด€๋ฆฌ๊ฐ€ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค. NestJS๋Š” ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ URI์˜ ๋ฒ„์ „์— ๋”ฐ๋ผ ํ•ด๋‹น ๋ฒ„์ „์„ ์ง€์›ํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์™€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ฐพ์•„์ฃผ๋ฏ€๋กœ, ํด๋ผ์ด์–ธํŠธ๋Š” /v1/... ๋˜๋Š” /v2/...์™€ ๊ฐ™์ด ์›ํ•˜๋Š” ๋ฒ„์ „์„ ๋ช…์‹œํ•˜์—ฌ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Swagger ์„ค์ • - ๋‹จ์ผ ๋ฌธ์„œ ํ†ตํ•ฉ

์ด์ œ Swagger๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋ฒ„์ „์ด ์ ์šฉ๋œ API ์—”๋“œํฌ์ธํŠธ๋“ค์„ ๋ฌธ์„œํ™”ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์•ž์„œ enableVersioning()์„ ๋จผ์ € ํ˜ธ์ถœํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด์ œ Swagger ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๊ฐ ๊ฒฝ๋กœ์— ์ž๋™์œผ๋กœ ๋ฒ„์ „ prefix๊ฐ€ ํฌํ•จ๋œ ์ƒํƒœ๋กœ ์ŠคํŽ™์ด ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค. ํ•œ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹์€ **"๋‹จ์ผ Swagger ๋ฌธ์„œ"**๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ชจ๋“  ๋ฒ„์ „์˜ API๋ฅผ ํ•œ๊บผ๋ฒˆ์— ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์†Œ๊ทœ๋ชจ ์„œ๋น„์Šค๋‚˜ API ๋ณ€๊ฒฝ ๋ฒ”์œ„๊ฐ€ ํฌ์ง€ ์•Š์€ ๊ฒฝ์šฐ, ํ•œ ๊ฐœ์˜ Swagger UI์—์„œ v1๊ณผ v2 ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋ชจ๋‘ ์—ด๋žŒํ•  ์ˆ˜ ์žˆ์–ด ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋‹จ์ผ ๋ฌธ์„œ ์ „๋žต์—์„œ๋Š” NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•œ ๋ฒˆ์˜ SwaggerModule.createDocument() ํ˜ธ์ถœ๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. DocumentBuilder๋ฅผ ์ด์šฉํ•ด ๊ธฐ๋ณธ์ ์ธ ์ •๋ณด(title, description, version ๋“ฑ)๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฌธ์„œ๋ฅผ ๋งŒ๋“  ๋’ค, SwaggerModule.setup()์œผ๋กœ Swagger UI ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ์•ž์„œ main.ts ์˜ˆ์‹œ์—์„œ๋Š” setupSwagger(app) ํ•จ์ˆ˜๋ฅผ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ–ˆ๋Š”๋ฐ, ๊ทธ ๊ตฌํ˜„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

// setupSwagger.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { INestApplication } from '@nestjs/common';

export function setupSwagger(app: INestApplication) {
  // Swagger ์„ค์ • ์ƒ์„ฑ
  const config = new DocumentBuilder()
    .setTitle('Reservation API')
    .setDescription('Reservation ์„œ๋น„์Šค API ๋ฌธ์„œ')
    .setVersion('1.0')
    .build();

  // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ Swagger ๋ฌธ์„œ ์ƒ์„ฑ (๋ชจ๋“  ๋ฒ„์ „ ํฌํ•จ)
  const document = SwaggerModule.createDocument(app, config);

  // Swagger UI ์—”๋“œํฌ์ธํŠธ ์„ค์ • (์˜ˆ: http://localhost:3000/docs)
  SwaggerModule.setup('docs', app, document);
}

์œ„ ์„ค์ •์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ๋“ฑ๋ก๋œ ๊ฒฝ๋กœ๋ฅผ ์Šค์บ”ํ•˜์—ฌ OpenAPI ๋ฌธ์„œ(document ๊ฐ์ฒด)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ์—๋Š” ํ˜„์žฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์กด์žฌํ•˜๋Š” v1, v2 ๋ชจ๋“  ๋ฒ„์ „์˜ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด v1๊ณผ v2 ๋‘ ๊ฐ€์ง€ ๋ฒ„์ „์˜ reservations ๊ฒฝ๋กœ๋“ค์ด ๊ฐ๊ฐ /v1/reservations, /v2/reservations๋กœ ๋ฌธ์„œํ™”๋˜์–ด ํ•œ Swagger UI์—์„œ ๋ชจ๋‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Swagger UI๋Š” ์œ„ ์˜ˆ์‹œ์—์„œ /docs ๊ฒฝ๋กœ๋กœ ์ œ๊ณต๋˜๋ฏ€๋กœ, ๋ธŒ๋ผ์šฐ์ €์—์„œ http://<์„œ๋ฒ„์ฃผ์†Œ>/docs๋กœ ์ ‘์†ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ : ์ „์—ญ ๊ฒฝ๋กœ(prefix) app.setGlobalPrefix()๋ฅผ ์‚ฌ์šฉํ•ด /api/v1 ํ˜•์‹์œผ๋กœ ๊ฒฝ๋กœ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ, Swagger ์„ค์ • ์‹œ ์ „์—ญ ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ๋ฌด์‹œํ•˜๊ฑฐ๋‚˜ ๊ฒฝ๋กœ๋ฅผ ์กฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. SwaggerModule.createDocument์˜ ์˜ต์…˜์œผ๋กœ ignoreGlobalPrefix: true๋ฅผ ์ฃผ๋ฉด ์ „์—ญ ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ๋ฌธ์„œ์—์„œ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๊ณ , ๋˜๋Š” SwaggerModule.setup()์˜ ๊ฒฝ๋กœ์— :version๊ณผ ๊ฐ™์€ ๋™์  ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์‚ฌ์šฉํ•ด Swagger UI๋ฅผ ๊ฐ ๋ฒ„์ „์— ๋งž๊ฒŒ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ์ผ ๋ฌธ์„œ ์ ‘๊ทผ ๋ฐฉ์‹์—์„œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํ•œ ๊ฐœ์˜ UI์—์„œ ๋ชจ๋“  ๋ฒ„์ „์„ ๋ณด์—ฌ์ฃผ์ง€๋งŒ, ํ•„์š”์— ๋”ฐ๋ผ ์ด๋Ÿฌํ•œ ์„ค์ •์œผ๋กœ Swagger UI ์ ‘๊ทผ ๊ฒฝ๋กœ๋ฅผ ํŠœ๋‹ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Swagger ์„ค์ • - ๋‹ค์ค‘ ๋ฌธ์„œ ๋ถ„๋ฆฌ

API ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๊ฑฐ๋‚˜, ๋ชจ๋…ธ๋ ˆํฌ(monorepo) ๊ตฌ์กฐ๋กœ ์—ฌ๋Ÿฌ ๋ชจ๋“ˆ์ด ๊ณต์กดํ•˜๋Š” ๊ฒฝ์šฐ ํ˜น์€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฒฝ๊ณ„๋ฅผ ๋‚˜๋ˆ  ๋ณ„๋„์˜ API ๋ฌธ์„œ๋ฅผ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” "๋‹ค์ค‘ Swagger ๋ฌธ์„œ" ์ „๋žต์ด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์—์„œ๋Š” API ๋ฒ„์ „๋ณ„ ๋˜๋Š” ๋ชจ๋“ˆ๋ณ„๋กœ ๋ณ„๋„์˜ Swagger ์ŠคํŽ™๊ณผ UI๋ฅผ ๋งŒ๋“ค์–ด ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, v1 API๋Š” /v1/docs์—์„œ ๋ฌธ์„œํ™”ํ•˜๊ณ  v2 API๋Š” /v2/docs์—์„œ ๋ณ„๋„ UI๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (ํ˜น์€ ์˜ˆ์•ฝ ์„œ๋น„์Šค์™€ ๊ฒฐ์ œ ์„œ๋น„์Šค๋ฅผ ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋ฌธ์„œ๋กœ ๋…ธ์ถœํ•˜๋Š” ์‹์œผ๋กœ ๋ชจ๋“ˆ๋ณ„ ๋ถ„๋ฆฌ๋„ ๊ฐ€๋Šฅ). ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํŠน์ • ๋ฒ„์ „์ด๋‚˜ ๋„๋ฉ”์ธ์— ํ•ด๋‹นํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋งŒ ์„ ๋ณ„ํ•˜์—ฌ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ์–ด ๋ฌธ์„œ๊ฐ€ ๋ฐฉ๋Œ€ํ•ด์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ๊ด€๋ฆฌ ํฌ์ธํŠธ๋ฅผ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

NestJS Swagger ๋ชจ๋“ˆ์€ ์ด๋Ÿฌํ•œ ๋‹ค์ค‘ ๋ช…์„ธ ์ง€์›์„ ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š”๋ฐ, ์ด๋ฅผ ์œ„ํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ชจ๋“ˆํ™”ํ•˜๊ณ  SwaggerModule.createDocument() ํ˜ธ์ถœ ์‹œ include ์˜ต์…˜์„ ํ™œ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. createDocument์˜ ์„ธ ๋ฒˆ์งธ ์ธ์ž๋กœ SwaggerDocumentOptions ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ, ๊ทธ ์•ˆ์— ํฌํ•จ์‹œํ‚ฌ ๋ชจ๋“ˆ ๋ชฉ๋ก์„ include ๋ฐฐ์—ด๋กœ ์ง€์ •ํ•˜๋ฉด ํ•ด๋‹น ๋ชจ๋“ˆ๋“ค์— ์†ํ•œ ๊ฒฝ๋กœ๋งŒ์„ ์Šค์บ”ํ•˜์—ฌ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ์˜ ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด CatsModule๊ณผ DogsModule์„ ๊ฐœ๋ณ„ ํฌํ•จํ•˜์—ฌ ๋‘ ๊ฐœ์˜ ์ŠคํŽ™์„ ๋งŒ๋“ค๊ณ , ๊ฐ๊ฐ ๋ณ„๋„์˜ Swagger UI ์—”๋“œํฌ์ธํŠธ์— ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ๋ฒ„์ „๋ณ„๋กœ ๋ชจ๋“ˆ์„ ๋ถ„๋ฆฌํ•˜์—ฌ Swagger ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

// ์˜ˆ์‹œ: v1, v2 ๋ชจ๋“ˆ์ด ๋ถ„๋ฆฌ๋œ ๊ฒฝ์šฐ์˜ Swagger ๋ฌธ์„œ ์„ค์ •
import { ReservationsModuleV1 } from './reservations-v1.module';
import { ReservationsModuleV2 } from './reservations-v2.module';

// ... (NestFactory๋กœ app ์ƒ์„ฑ ๋“ฑ์€ ์ƒ๋žต)

const v1Config = new DocumentBuilder()
  .setTitle('Reservation API - v1')
  .setDescription('Reservation API (๋ฒ„์ „ 1)')
  .setVersion('1.0')
  .build();

// v1 ๋ชจ๋“ˆ๋งŒ ํฌํ•จํ•œ Swagger ๋ฌธ์„œ ์ƒ์„ฑ
const documentV1 = SwaggerModule.createDocument(app, v1Config, {
  include: [ReservationsModuleV1],
});
SwaggerModule.setup('api/v1/docs', app, documentV1);  // v1 ๋ฌธ์„œ UI ์—”๋“œํฌ์ธํŠธ

const v2Config = new DocumentBuilder()
  .setTitle('Reservation API - v2')
  .setDescription('Reservation API (๋ฒ„์ „ 2)')
  .setVersion('2.0')
  .build();

// v2 ๋ชจ๋“ˆ๋งŒ ํฌํ•จํ•œ Swagger ๋ฌธ์„œ ์ƒ์„ฑ
const documentV2 = SwaggerModule.createDocument(app, v2Config, {
  include: [ReservationsModuleV2],
});
SwaggerModule.setup('api/v2/docs', app, documentV2);  // v2 ๋ฌธ์„œ UI ์—”๋“œํฌ์ธํŠธ

์œ„ ์ฝ”๋“œ์—์„œ๋Š” ReservationsModuleV1๊ณผ ReservationsModuleV2 ๋‘ ๋ชจ๋“ˆ์„ ๊ฐ€์ •ํ•˜๊ณ , ๊ฐ๊ฐ์˜ ๋ชจ๋“ˆ์„ ํฌํ•จํ•˜๋Š” ๋ณ„๋„์˜ Swagger ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. include ์˜ต์…˜์— ๋ฐฐ์—ด๋กœ ๋ชจ๋“ˆ์„ ๋„˜๊ธฐ๋ฉด ํ•ด๋‹น ๋ชจ๋“ˆ ๋ฐ ๊ทธ ํ•˜์œ„์— ์—ฐ๊ฒฐ๋œ ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ ๊ฒฝ๋กœ๋“ค๋งŒ ๋ฐ˜์˜๋œ OpenAPI ์ŠคํŽ™์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ƒ์„ฑ๋œ documentV1, documentV2๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฒฝ๋กœ(/api/v1/docs, /api/v2/docs)์— SwaggerModule.setup()์œผ๋กœ ์„ค์ •ํ•˜์—ฌ, ๋ฒ„์ „ 1์šฉ Swagger UI์™€ ๋ฒ„์ „ 2์šฉ Swagger UI๋ฅผ ๋ถ„๋ฆฌํ•ด ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ํด๋ผ์ด์–ธํŠธ๋‚˜ ๊ฐœ๋ฐœ์ž๋Š” /api/v1/docs์—์„œ v1 API๋งŒ, /api/v2/docs์—์„œ v2 API๋งŒ ๊ฐ๊ฐ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŒ: ์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋ฒ„์ „๋ฟ ์•„๋‹ˆ๋ผ ๊ธฐ๋Šฅ๋ณ„ ๋ชจ๋“ˆ๋กœ ๋ฌธ์„œ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด _Public API_์™€ _Admin API_๋ฅผ ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋ชจ๋“ˆ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด์„œ Swagger ๋ฌธ์„œ๋ฅผ ๋ณ„๋„๋กœ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ณ„๋กœ ๋ฌธ์„œ๋ฅผ ๋”ฐ๋กœ ์ƒ์„ฑํ•˜์—ฌ ์„œ๋น„์Šค ๊ฒฝ๊ณ„๋งˆ๋‹ค ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. include ์˜ต์…˜์— ์›ํ•˜๋Š” ๋ชจ๋“ˆ ์กฐํ•ฉ์„ ๋„˜๊ธฐ๋ฉด ํ•„์š”ํ•œ ์—”๋“œํฌ์ธํŠธ ๊ทธ๋ฃน๋งŒ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ ์ ‘๊ทผ ๋ฐฉ์‹์˜ ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค

์•ž์„œ ์‚ดํŽด๋ณธ ๋‹จ์ผ ๋ฌธ์„œ ํ†ตํ•ฉ๊ณผ ๋‹ค์ค‘ ๋ฌธ์„œ ๋ถ„๋ฆฌ ๋‘ ๊ฐ€์ง€ Swagger ํ†ตํ•ฉ ์ „๋žต์€ ๊ฐ๊ฐ ์žฅ๋‹จ์ ์ด ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉํ•  ์ƒํ™ฉ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ์™€ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์•Œ๋งž์€ ๋ฐฉ์‹์„ ์„ ํƒํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

์š”์•ฝํ•˜๋ฉด, ์ž‘์€ ๊ทœ๋ชจ๋‚˜ ๋‹จ์ผ ์„œ๋น„์Šค๋ผ๋ฉด ๋ชจ๋“  ๋ฒ„์ „์„ ํ•˜๋‚˜์˜ Swagger ๋ฌธ์„œ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ํŽธ์ด ๊ฐ„ํŽธํ•˜๋ฉฐ, ๋Œ€๊ทœ๋ชจ ์„œ๋น„์Šค๋‚˜ ๋‹ค์ˆ˜์˜ ๋ชจ๋“ˆ์„ ๊ฐ–๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ฒ„์ „/๋ชจ๋“ˆ๋ณ„๋กœ Swagger ๋ฌธ์„œ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ๋ช…ํ™•์„ฑ์„ ๋†’์ด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋‘ ์ ‘๊ทผ ๋ฐฉ์‹ ๋ชจ๋‘ NestJS์—์„œ ๊ณต์‹์ ์œผ๋กœ ์ง€์›๋˜๋ฏ€๋กœ, ํ•„์š”์— ๋”ฐ๋ผ ์ ์ ˆํžˆ ์„ ํƒํ•˜์—ฌ ํ™œ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ฐ ๋ฐฉ๋ฒ•์„ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ๋Š” ์œ„์—์„œ ์‚ดํŽด๋ณธ ๋Œ€๋กœ ๋น„๊ต์  ๊ฐ„๋‹จํ•˜๋ฉฐ, NestJS์˜ ๋ฒ„์ „ ๊ด€๋ฆฌ ๋ฐ Swagger ๋ชจ๋“ˆ ๊ธฐ๋Šฅ์„ ์กฐํ•ฉํ•˜์—ฌ ์œ ์—ฐํ•˜๊ฒŒ API ๋ฌธ์„œ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋งŒ ํ•œ๋‹ค๋ฉด, NestJS๋Š” ๋ฒ„์ „๋ณ„๋กœ ํ˜ธํ™˜์„ฑ ์œ ์ง€์™€ ๋ฌธ์„œํ™”๋ฅผ ์†์‰ฝ๊ฒŒ ์ง€์›ํ•˜๋ฏ€๋กœ, ์•ˆ์‹ฌํ•˜๊ณ  API๋ฅผ ๋ฐœ์ „์‹œ์ผœ ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฐธ๊ณ  ์ž๋ฃŒ: NestJS ๊ณต์‹ ๋ฌธ์„œ ๋ฐ GitHub ์ด์Šˆ ๋“ฑ์„ ํ†ตํ•ด API ๋ฒ„์ €๋‹ ๋ฐ Swagger ํ†ตํ•ฉ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: NestJS Docs - Versioning, OpenAPI/Swagger ์„น์…˜) ์ด๋ฒˆ ๊ฐ€์ด๋“œ์—์„œ ๋‹ค๋ฃฌ ๋‚ด์šฉ์€ ํ˜„์—…์—์„œ๋„ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด์œผ๋กœ, NestJS ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ์˜ API ์„ค๊ณ„์™€ ๋ฌธ์„œ ์ „๋žต ์ˆ˜๋ฆฝ์— ๋„์›€์ด ๋˜๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.